package com.xzzz.irda.guardcat.core.metric.thread;

import java.io.Serializable;
import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.util.concurrent.locks.LockSupport;

/**
 * 线程信息
 *
 * @author xzzz
 * @since 1.0.0
 */
public class ThreadMsg implements Serializable, Comparable<ThreadMsg> {
    private static final long serialVersionUID = 1L;

    private static final int MAX_FRAMES = 8;

    /**
     * 尚未启动的线程的线程状态
     */
    private static final String NEW = "NEW";

    /**
     * 可运行线程的线程状态
     * 处于可运行状态的线程正在 Java 虚拟机中执行，但它可能正在等待来自操作系统的其他资源，例如处理器。
     */
    private static final String RUNNABLE = "RUNNABLE";

    /**
     * 线程阻塞等待监视器锁的线程状态。
     * 处于阻塞状态的线程正在等待监视器锁进入同步块方法或调用 {@link Object#wait() Object.wait} 后重新进入同步块方法。
     */
    private static final String BLOCKED = "BLOCKED";

    /**
     * 等待线程的线程状态。
     * 由于调用以下方法之一，线程处于等待状态：
     * 1、{@link Object#wait() Object.wait} with no timeout
     * 2、{@link Thread#join() Thread.join } with no timeout
     * 3、{@link LockSupport#park() LockSupport.park}
     * <p>
     * 处于等待状态的线程正在等待另一个线程执行特定操作。
     * 1、在一个对象上调用了 Object.wait() 的线程
     * 正在等待另一个线程调用 Object.notify() 或 Object.notifyAll() 在那个对象上。
     * 2、已调用 Thread.join() 的线程正在等待指定线程终止。
     */
    private static final String WAITING = "WAITING";

    /**
     * 具有指定等待时间的等待线程的线程状态。
     * 由于以指定的正等待时间调用以下方法之一，线程处于定时等待状态：
     * 1、{@link Thread#sleep Thread.sleep}
     * 2、{@link Object#wait(long) Object.wait} with timeout
     * 3、{@link Thread#join(long) Thread.join} with timeout
     * 4、{@link LockSupport#parkNanos LockSupport.parkNanos}
     * 5、{@link LockSupport#parkUntil LockSupport.parkUntil}
     */
    private static final String TIMED_WAITING = "TIMED_WAITING";

    /**
     * 已终止线程的线程状态。线程已完成执行。
     */
    private static final String TERMINATED = "TERMINATED";

    /**
     * 线程ID
     */
    private final long threadId;

    /**
     * 线程名称
     */
    private final String threadName;

    /**
     * 线程进入 BLOCKED 状态的-近似-累计时间【单位：毫秒】
     */
    private final long blockedTime;

    /**
     * 线程处于 BLOCKED 状态的总次数。
     */
    private final long blockedCount;

    /**
     * 线程处于 WAITING 或 TIMED_WAITING 状态的-近似-累计时间【单位：毫秒】
     */
    private final long waitedTime;

    /**
     * 线程处于 WAITING 或 TIMED_WAITING 状态的总次数。
     */
    private final long waitedCount;

    /**
     * 锁
     */
    private final LockInfo lock;

    /**
     * 锁名称
     */
    private final String lockName;

    /**
     * 拥有锁的线程的id
     */
    private final long lockOwnerId;

    /**
     * 拥有锁的线程的名称
     */
    private final String lockOwnerName;

    /**
     * 是否本地
     */
    private final boolean inNative;

    /**
     * 是否挂起
     */
    private final boolean suspended;

    /**
     * 线程状态
     */
    private final String threadState;

    /**
     * 线程堆栈信息
     */
    private final StackTraceElement[] stackTrace;

    /**
     * 有关对象监视器锁的信息【synchronize 关键字锁】
     */
    private final MonitorInfo[] lockedMonitors;

    /**
     * 所有的锁信息
     */
    private final LockInfo[] lockedSynchronizers;

    /**
     * 线程在用户模式或系统模式下中的执行 CPU 时间（以纳秒为单位）
     */
    private final long threadCpuTime;

    /**
     * 线程在用户模式下的执行 CPU 时间（以纳秒为单位）
     */
    private final long threadUserTime;

    public ThreadMsg(ThreadInfo threadInfo, long threadCpuTime, long threadUserTime) {
        this.threadId = threadInfo.getThreadId();
        this.threadName = threadInfo.getThreadName();
        this.blockedTime = threadInfo.getBlockedTime();
        this.blockedCount = threadInfo.getBlockedCount();
        this.waitedTime = threadInfo.getWaitedTime();
        this.waitedCount = threadInfo.getWaitedCount();
        this.lock = threadInfo.getLockInfo();
        this.lockName = threadInfo.getLockName();
        this.lockOwnerId = threadInfo.getLockOwnerId();
        this.lockOwnerName = threadInfo.getLockOwnerName();
        this.inNative = threadInfo.isInNative();
        this.suspended = threadInfo.isSuspended();
        this.threadState = threadInfo.getThreadState().toString();
        this.stackTrace = threadInfo.getStackTrace();
        this.lockedMonitors = threadInfo.getLockedMonitors();
        this.lockedSynchronizers = threadInfo.getLockedSynchronizers();
        this.threadCpuTime = threadCpuTime;
        this.threadUserTime = threadUserTime;
    }

    @Override
    public int compareTo(ThreadMsg threadMsg) {
        if (null == threadMsg) {
            return 1;
        }
        long userTime = threadMsg.getThreadUserTime();

        if (userTime < threadUserTime) {
            return -1;
        }

        if (userTime == threadUserTime) {
            long cpuTime = threadMsg.getThreadCpuTime();
            if (cpuTime < threadCpuTime) {
                return -1;
            }
            if (cpuTime == threadCpuTime) {
                long tid = threadMsg.getThreadId();
                if (tid < threadId) {
                    return -1;
                }
            }
        }

        return 1;
    }

    public long getThreadId() {
        return threadId;
    }

    public String getThreadName() {
        return threadName;
    }

    public long getBlockedTime() {
        return blockedTime;
    }

    public long getBlockedCount() {
        return blockedCount;
    }

    public long getWaitedTime() {
        return waitedTime;
    }

    public long getWaitedCount() {
        return waitedCount;
    }

    public LockInfo getLock() {
        return lock;
    }

    public String getLockName() {
        return lockName;
    }

    public long getLockOwnerId() {
        return lockOwnerId;
    }

    public String getLockOwnerName() {
        return lockOwnerName;
    }

    public boolean isInNative() {
        return inNative;
    }

    public boolean isSuspended() {
        return suspended;
    }

    public String getThreadState() {
        return threadState;
    }

    public StackTraceElement[] getStackTrace() {
        return stackTrace;
    }

    public MonitorInfo[] getLockedMonitors() {
        return lockedMonitors;
    }

    public LockInfo[] getLockedSynchronizers() {
        return lockedSynchronizers;
    }

    public long getThreadCpuTime() {
        return threadCpuTime;
    }

    public long getThreadUserTime() {
        return threadUserTime;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("\"" + threadName + "\"" + " Id=" + threadId + " " + threadState);
        if (lockName != null) {
            sb.append(" on ").append(lockName);
        }
        if (lockOwnerName != null) {
            sb.append(" owned by \"").append(lockOwnerName).append("\" Id=").append(lockOwnerId);
        }
        if (suspended) {
            sb.append(" (suspended)");
        }
        if (inNative) {
            sb.append(" (in native)");
        }
        sb.append(", total CPU execute Time ").append(threadCpuTime).append(" nanoseconds");
        sb.append(", in user mode CPU execute Time ").append(threadUserTime).append(" nanoseconds");
        sb.append('\n');
        int i = 0;
        for (; i < stackTrace.length && i < MAX_FRAMES; i++) {
            StackTraceElement ste = stackTrace[i];
            sb.append("\tat ").append(ste.toString());
            sb.append('\n');
            if (i == 0 && lock != null) {
                String ts = threadState;
                switch (ts) {
                    case BLOCKED:
                        sb.append("\t-  blocked on ").append(lock);
                        sb.append('\n');
                        break;
                    case WAITING:
                    case TIMED_WAITING:
                        sb.append("\t-  waiting on ").append(lock);
                        sb.append('\n');
                        break;
                    default:
                }
            }

            for (MonitorInfo mi : lockedMonitors) {
                if (mi.getLockedStackDepth() == i) {
                    sb.append("\t-  locked ").append(mi);
                    sb.append('\n');
                }
            }
        }
        if (i < stackTrace.length) {
            sb.append("\t...");
            sb.append('\n');
        }

        LockInfo[] locks = lockedSynchronizers;
        if (locks.length > 0) {
            sb.append("\n\tNumber of locked synchronizers = ").append(locks.length);
            sb.append('\n');
            for (LockInfo li : locks) {
                sb.append("\t- ").append(li);
                sb.append('\n');
            }
        }
        sb.append('\n');
        return sb.toString();
    }

    @Override
    public int hashCode() {
        return String.valueOf(threadId).hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ThreadMsg)) {
            return false;
        }
        ThreadMsg threadMsg = (ThreadMsg) obj;
        if (this == threadMsg) {
            return true;
        }
        return threadId == ((ThreadMsg) obj).threadId;
    }
}
